// fetch-stream-loader.js-FetchStreamLoader类-fetch加载器
import Log from '../utils/logger.js';
import Browser from '../utils/browser.js';
import {BaseLoader, LoaderStatus, LoaderErrors} from './loader.js';
import {RuntimeException} from '../utils/exception.js';

/* fetch + stream IO loader. Currently working on chrome 43+.
 * fetch provides a better alternative http API to XMLHttpRequest
 *
 * fetch spec   https://fetch.spec.whatwg.org/
 * stream spec  https://streams.spec.whatwg.org/
 */
class FetchStreamLoader extends BaseLoader {
    // 是否支持 fetch 流加载：浏览器非 IE、全局有 fetch 方法和 ReadableStream 属性
    static isSupported() {
        try {
            // fetch + stream is broken on Microsoft Edge. Disable before build 15048.
            // see https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/8196907/
            // Fixed in Jan 10, 2017. Build 15048+ removed from blacklist.
            let isWorkWellEdge = Browser.msedge && Browser.version.minor >= 15048;
            let browserNotBlacklisted = Browser.msedge ? isWorkWellEdge : true;
            return (self.fetch && self.ReadableStream && browserNotBlacklisted);
        } catch (e) {
            return false;
        }
    }

    // 属性
    constructor(seekHandler, config) {
        super('fetch-stream-loader');
        // 实例构造器名称
        this.TAG = 'FetchStreamLoader';
        // _seekHandler 搜索处理函数
        this._seekHandler = seekHandler;
        this._config = config;
        // 需要暂存区，重写为 true
        this._needStash = true;
        // 请求终止标志位，初始化为 false
        this._requestAbort = false;
        // 内容长度，初始化为 null
        this._contentLength = null;
        // 已接收长度，初始化为 0
        this._receivedLength = 0;
    }

    // 方法
    // destroy() 销毁实例
    destroy() {
        // 如果实例处于工作中
        if (this.isWorking()) {
            // 终止加载
            this.abort();
        }
        // 执行基类的 destroy 方法
        super.destroy();
    }
    // 加载数据，传入 dataSource 和 range 两个参数
    open(dataSource, range) {
        // 设置实例的数据源为 dataSource
        this._dataSource = dataSource;
        // 实例的范围为 range
        this._range = range;
        //  获取实例的搜索处理函数配置
        let sourceURL = dataSource.url;
        if (this._config.reuseRedirectedURL && dataSource.redirectedURL != undefined) {
            sourceURL = dataSource.redirectedURL;
        }
        // 获取实例的搜索处理函数配置（之后简称搜索配置）
        let seekConfig = this._seekHandler.getConfig(sourceURL, range);
        // new 一个 Headers 实例（之后简称 headers）
        let headers = new self.Headers();
        // 如果搜索配置的 headers 属性是一个对象
        if (typeof seekConfig.headers === 'object') {
            let configHeaders = seekConfig.headers;
            // 遍历这个属性，将它自身的键值对添加到 headers 里
            for (let key in configHeaders) {
                if (configHeaders.hasOwnProperty(key)) {
                    headers.append(key, configHeaders[key]);
                }
            }
        }
        // 创建一个参数，等下 ajax 时用
        let params = {
            method: 'GET',
            headers: headers,
            mode: 'cors',
            cache: 'default',
            // The default policy of Fetch API in the whatwg standard
            // Safari incorrectly indicates 'no-referrer' as default policy, fuck it
            referrerPolicy: 'no-referrer-when-downgrade'
        };
        // 添加附加的headers
        if (typeof this._config.headers === 'object') {
            for (let key in this._config.headers) {
                headers.append(key, this._config.headers[key]);
            }
        }
        // 如果 dataSource 的跨域属性为 false
        if (dataSource.cors === false) {
            // 设置参数的模式为 same-origin
            params.mode = 'same-origin';
        }
        // 如果 dataSource 的证书属性为 false
        if (dataSource.withCredentials) {
            // 设置参数的证书为 include
            params.credentials = 'include';
        }
        // 来自配置的referrerPolicy
        if (dataSource.referrerPolicy) {
            params.referrerPolicy = dataSource.referrerPolicy;
        }
        // 设置实例的状态为连接中
        this._status = LoaderStatus.kConnecting;
        // 通过 fetch 实现 ajax，传入搜索配置中的 url 和参数获取响应数据
        self.fetch(seekConfig.url, params).then((res) => {
            // 如果实例的请求终止标志位为 true
            if (this._requestAbort) {
                // 设置实例的请求终止标志位为 false
                this._requestAbort = false;
                // 设置实例的状态为闲置
                this._status = LoaderStatus.kIdle;
                // 结束
                return;
            }
            // 如果响应数据接收成功且状态码为 2xx
            if (res.ok && (res.status >= 200 && res.status <= 299)) {
                // 如果响应数据头长度不为 0 且实例存在已知内容长度事件处理函数，执行之
                if (res.url !== seekConfig.url) {
                    if (this._onURLRedirect) {
                        let redirectedURL = this._seekHandler.removeURLParameters(res.url);
                        this._onURLRedirect(redirectedURL);
                    }
                }

                let lengthHeader = res.headers.get('Content-Length');
                if (lengthHeader != null) {
                    this._contentLength = parseInt(lengthHeader);
                    if (this._contentLength !== 0) {
                        if (this._onContentLengthKnown) {
                            this._onContentLengthKnown(this._contentLength);
                        }
                    }
                }
                // 执行抽取函数，传入通过响应数据体的 getReader 方法获取的对象
                return this._pump.call(this, res.body.getReader());
            } else {
                // 设置实例的状态为出错
                this._status = LoaderStatus.kError;
                // 如果实例存在出错事件处理函数
                if (this._onError) {
                    // 执行，传入 HTTP 状态码错误和错误数据
                    this._onError(LoaderErrors.HTTP_STATUS_CODE_INVALID, {code: res.status, msg: res.statusText});
                } else {
                    // 否则报 HTTP 状态码无效错误
                    throw new RuntimeException('FetchStreamLoader: Http code invalid, ' + res.status + ' ' + res.statusText);
                }
            }
        // 若捕捉到错误
        }).catch((e) => {
            // 设置实例的状态为出错
            this._status = LoaderStatus.kError;
            // 如果实例存在出错事件处理函数
            if (this._onError) {
                // 执行，传入其他错误和错误数据
                this._onError(LoaderErrors.EXCEPTION, {code: -1, msg: e.message});
            } else {
                // 否则抛出错误
                throw e;
            }
        });
    }
    // 终止加载器
    abort() {
        // 设置当前实例的请求终止标志位为 true
        this._requestAbort = true;
    }
    // 抽取数据，传入 reader 参数
    _pump(reader) {  // ReadableStreamReader
        // 返回一个 Promise，为 reader 调用 read() 后的 result
        return reader.read().then((result) => {
            // 如果 result 的 done 为 true
            if (result.done) {
                // 设置实例的状态为完成
                if (this._contentLength !== null && this._receivedLength < this._contentLength) {
                    // Report Early-EOF
                    this._status = LoaderStatus.kError;
                    let type = LoaderErrors.EARLY_EOF;
                    let info = {code: -1, msg: 'Fetch stream meet Early-EOF'};
                    if (this._onError) {
                        this._onError(type, info);
                    } else {
                        throw new RuntimeException(info.msg);
                    }
                } else {
                    this._status = LoaderStatus.kComplete;
                    // 如果实例存在完成事件处理函数
                    if (this._onComplete) {
                        // 执行，传入实例范围起点和实例范围起点 + 实例接受数据长度 - 1
                        this._onComplete(this._range.from, this._range.from + this._receivedLength - 1);
                    }
                }
            } else {
                //  如果实例的请求终止标志位为 true
                if (this._requestAbort === true) {
                    // 设置实例的请求终止标志位为 false
                    this._requestAbort = false;
                    // 设置实例的状态为完成
                    this._status = LoaderStatus.kComplete;
                    // 返回 reader.cancel()
                    return reader.cancel();
                }
                // 设置实例的状态为缓冲中
                this._status = LoaderStatus.kBuffering;
                // 给实例的接收长度加上 result.value.buffer 的字节长度
                let chunk = result.value.buffer;
                let byteStart = this._range.from + this._receivedLength;
                this._receivedLength += chunk.byteLength;
                // 如果实例存在完成数据到达处理函数，执行之，传入 result.value.buffer、实例范围起点 + 实例接收长度、实例接收长度
                if (this._onDataArrival) {
                    // 返回执行抽取函数的结果，传入 reader
                    this._onDataArrival(chunk, byteStart, this._receivedLength);
                }

                this._pump(reader);
            }
        // 若捕捉到错误
        }).catch((e) => {
            if (e.code === 11 && Browser.msedge) {  // InvalidStateError on Microsoft Edge
                // Workaround: Edge may throw InvalidStateError after ReadableStreamReader.cancel() call
                // Ignore the unknown exception.
                // Related issue: https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/11265202/
                return;
            }
            // 设置实例的状态为出错
            this._status = LoaderStatus.kError;
            // 创建错误类型，初始化为 0
            let type = 0;
            // 创建错误信息，初始化为 null
            let info = null;
            // 如果错误码为 19 且 （实例内容长度为空或实例接收长度小于实例内容长度），错误类型为加载器过早结束，错误信息同理
            if ((e.code === 19 || e.message === 'network error') && // NETWORK_ERR
                (this._contentLength === null ||
                (this._contentLength !== null && this._receivedLength < this._contentLength))) {
                type = LoaderErrors.EARLY_EOF;
                info = {code: e.code, msg: 'Fetch stream meet Early-EOF'};
            // 否则错误类型为加载器其他错误，错误信息同理
            } else {
                type = LoaderErrors.EXCEPTION;
                info = {code: e.code, msg: e.message};
            }
            // 如果实例存在出错事件处理函数
            if (this._onError) {
                // 执行，传入错误类型和错误信息
                this._onError(type, info);
            } else {
                // 否则抛出运行时错误，传入错误信息
                throw new RuntimeException(info.msg);
            }
        });
    }

}

export default FetchStreamLoader;
